Add [DynamoDBEvent] annotation attribute and source generator support#2320
Add [DynamoDBEvent] annotation attribute and source generator support#2320GarrettBeatty wants to merge 1 commit intodevfrom
Conversation
d022bf4 to
4e6ca78
Compare
- DynamoDBEventAttribute with Stream, ResourceName, BatchSize, StartingPosition, MaximumBatchingWindowInSeconds, Filters, Enabled - DynamoDBEventAttributeBuilder for Roslyn AttributeData parsing - Source generator wiring (TypeFullNames, SyntaxReceiver, EventTypeBuilder, AttributeModelBuilder) - CloudFormationWriter ProcessDynamoDBAttribute (SAM DynamoDB event source mapping) - LambdaFunctionValidator ValidateDynamoDBEvents - DiagnosticDescriptors InvalidDynamoDBEventAttribute (AWSLambda0132) - DynamoDBEventAttributeTests (attribute unit tests) - DynamoDBEventsTests (CloudFormation writer tests) - E2E source generator snapshot tests - Integration test (DynamoDBEventSourceMapping) - Sample function (DynamoDbStreamProcessing) - .autover change file - README documentation
4e6ca78 to
39d776c
Compare
There was a problem hiding this comment.
Pull request overview
Adds DynamoDB Streams trigger support to Lambda Annotations via a new [DynamoDBEvent] attribute and extends the source generator to validate usage and emit the corresponding SAM/CloudFormation DynamoDB event configuration.
Changes:
- Introduces
DynamoDBEventAttribute(including validation and derived resource naming) inAmazon.Lambda.Annotations.DynamoDB. - Extends the source generator to recognize/build/validate DynamoDB events and write the SAM template event mapping.
- Adds unit, snapshot, and integration tests plus a test serverless app DynamoDB table and handler.
Reviewed changes
Copilot reviewed 27 out of 27 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| Libraries/test/TestServerlessApp/serverless.template | Adds a DynamoDB table with Streams enabled for integration testing. |
| Libraries/test/TestServerlessApp/TestServerlessApp.csproj | References Amazon.Lambda.DynamoDBEvents for the test app. |
| Libraries/test/TestServerlessApp/DynamoDbStreamProcessing.cs | Adds a sample DynamoDB stream Lambda handler using the new attribute. |
| Libraries/test/TestServerlessApp/DynamoDBEventExamples/ValidDynamoDBEvents.cs.txt | Adds valid attribute usage inputs for source generator snapshot tests. |
| Libraries/test/TestServerlessApp.IntegrationTests/TestServerlessApp.IntegrationTests.csproj | Adds DynamoDB SDK dependency for integration validation. |
| Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs | Fetches DynamoDB Stream ARN from the deployed test table; updates expected function count. |
| Libraries/test/TestServerlessApp.IntegrationTests/DynamoDBEventSourceMapping.cs | New integration test validating the deployed event source mapping config. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/DynamoDBEventsTests.cs | Adds CloudFormation writer tests for DynamoDB event rendering and sync behavior. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs | Adds snapshot-based source generator test coverage for DynamoDB events. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/ServerlessTemplates/dynamoDBEvents.template | Snapshot of generated template containing DynamoDB events. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/DynamoDB/ValidDynamoDBEvents_ProcessMessages_Generated.g.cs | Snapshot of generated handler for sync DynamoDB event usage. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Snapshots/DynamoDB/ValidDynamoDBEvents_ProcessMessagesAsync_Generated.g.cs | Snapshot of generated handler for async DynamoDB event usage. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/DynamoDBEventAttributeTests.cs | Adds unit tests for attribute defaults, tracking, and validation. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/CSharpSourceGeneratorVerifier.cs | Adds DynamoDBEvents assembly reference to generator test harness. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/Amazon.Lambda.Annotations.SourceGenerators.Tests.csproj | Adds project reference to Amazon.Lambda.DynamoDBEvents for tests. |
| Libraries/src/Amazon.Lambda.Annotations/README.md | Documents the new DynamoDB event attribute and usage. |
| Libraries/src/Amazon.Lambda.Annotations/DynamoDB/DynamoDBEventAttribute.cs | Implements the new [DynamoDBEvent] attribute and validation logic. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs | Emits DynamoDB event definitions into the generated template. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Validation/LambdaFunctionValidator.cs | Adds dependency + signature + attribute-property validation for DynamoDB events. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs | Adds DynamoDB type/attribute full-name constants and recognized attribute list entry. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs | Registers DynamoDBEventAttribute as a recognized annotation. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/EventTypeBuilder.cs | Adds mapping from DynamoDBEventAttribute to EventType.DynamoDB. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/DynamoDBEventAttributeBuilder.cs | Builds DynamoDBEventAttribute model from Roslyn AttributeData. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/AttributeModelBuilder.cs | Routes DynamoDBEventAttribute to the new builder/model. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs | Adds a new diagnostic descriptor for invalid DynamoDBEventAttribute. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/AnalyzerReleases.Unshipped.md | Records the newly added diagnostic in analyzer release notes. |
| .autover/changes/add-dynamodbevent-annotation.json | Adds an autover change entry for the new feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| if (!Stream.StartsWith("@")) | ||
| { | ||
| if (!Stream.Contains(":dynamodb:") && !Stream.Contains("/stream/")) |
There was a problem hiding this comment.
Validate() treats a stream ARN as valid if it contains either ":dynamodb:" or "/stream/" because the condition uses &&. This will let invalid ARNs through (e.g., missing /stream/). Update validation to require both parts (or parse the ARN structure similarly to SQSEventAttribute.Validate()).
| if (!Stream.Contains(":dynamodb:") && !Stream.Contains("/stream/")) | |
| if (!Stream.Contains(":dynamodb:") || !Stream.Contains("/stream/")) |
| if (Stream.StartsWith("@")) | ||
| { | ||
| return Stream.Substring(1); |
There was a problem hiding this comment.
Stream can be null (attributes can be invoked with null string constants), but ResourceName/Validate() call Stream.StartsWith(...) unconditionally. This can throw and crash the generator. Add a null/empty check (and return a validation error) before dereferencing Stream in ResourceName and Validate() (and consider rejecting "@" with no resource name).
| if (Stream.StartsWith("@")) | |
| { | |
| return Stream.Substring(1); | |
| if (string.IsNullOrWhiteSpace(Stream)) | |
| { | |
| return string.Empty; | |
| } | |
| if (Stream.StartsWith("@")) | |
| { | |
| return Stream.Length > 1 ? Stream.Substring(1) : string.Empty; |
| /// <summary> | ||
| /// The maximum number of records in each batch that Lambda pulls from the stream. | ||
| /// Default value is 100. | ||
| /// </summary> | ||
| public uint BatchSize | ||
| { | ||
| get => batchSize.GetValueOrDefault(); | ||
| set => batchSize = value; | ||
| } | ||
| private uint? batchSize { get; set; } | ||
| internal bool IsBatchSizeSet => batchSize.HasValue; |
There was a problem hiding this comment.
The BatchSize XML comment says the default is 100, but when unset the property returns 0 (because GetValueOrDefault() is used). Either initialize batchSize to 100, change the getter to GetValueOrDefault(100), or update the comment to clarify that the property is omitted from the template unless explicitly set and SAM/Lambda applies the default.
| /// <summary> | ||
| /// If set to false, the event source mapping will be disabled. Default value is true. | ||
| /// </summary> | ||
| public bool Enabled | ||
| { | ||
| get => enabled.GetValueOrDefault(); | ||
| set => enabled = value; | ||
| } | ||
| private bool? enabled { get; set; } | ||
| internal bool IsEnabledSet => enabled.HasValue; | ||
|
|
There was a problem hiding this comment.
The Enabled XML comment says the default is true, but when unset the property returns false (GetValueOrDefault()). This is surprising if user code reflects over the attribute, and it also conflicts with the stated default. Consider using GetValueOrDefault(true) or clarifying in the comment that the property is omitted from the generated template unless explicitly set (so the AWS default applies).
| if (!string.IsNullOrEmpty(StartingPosition) && StartingPosition != "TRIM_HORIZON" && StartingPosition != "LATEST") | ||
| { | ||
| validationErrors.Add($"{nameof(DynamoDBEventAttribute.StartingPosition)} = {StartingPosition}. It must be either TRIM_HORIZON or LATEST"); | ||
| } |
There was a problem hiding this comment.
Validate() allows StartingPosition to be null/empty (no error), but CloudFormationWriter always writes StartingPosition to the template. If a user sets StartingPosition = null, the generated SAM template will contain an invalid null value. Treat null/empty as invalid or coerce it back to the default (LATEST) during validation/building.
| <ItemGroup> | ||
| <!-- AWSSDK.SecurityToken is needed at runtime for environments which uses assume-role operation for credentials --> | ||
| <PackageReference Include="AWSSDK.SecurityToken" Version="3.7.1.99" /> | ||
| <PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.*" /> |
There was a problem hiding this comment.
The integration test project uses a floating NuGet version (3.7.*) for AWSSDK.DynamoDBv2, which can make CI builds non-reproducible and introduce unexpected behavior when a new patch is released. Pin this to a specific version (consistent with AWSSDK.SecurityToken in the same file).
| <PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.*" /> | |
| <PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.1.99" /> |
| Console.WriteLine($"[IntegrationTest] TestTable: {testTableName}"); | ||
| if (!string.IsNullOrEmpty(testTableName)) | ||
| { | ||
| var dynamoDbClient = new Amazon.DynamoDBv2.AmazonDynamoDBClient(Amazon.RegionEndpoint.USWest2); |
There was a problem hiding this comment.
AmazonDynamoDBClient is created but never disposed. Even in tests, it's better to dispose AWS SDK clients (or reuse a shared client) to avoid socket exhaustion/resource leaks across test runs; consider using var dynamoDbClient = ... or keeping a single client on the fixture like the other helpers.
| var dynamoDbClient = new Amazon.DynamoDBv2.AmazonDynamoDBClient(Amazon.RegionEndpoint.USWest2); | |
| using var dynamoDbClient = new Amazon.DynamoDBv2.AmazonDynamoDBClient(Amazon.RegionEndpoint.USWest2); |
| public static readonly DiagnosticDescriptor InvalidDynamoDBEventAttribute = new DiagnosticDescriptor(id: "AWSLambda0137", | ||
| title: "Invalid DynamoDBEventAttribute", | ||
| messageFormat: "Invalid DynamoDBEventAttribute encountered: {0}", | ||
| category: "AWSLambdaCSharpGenerator", | ||
| DiagnosticSeverity.Error, | ||
| isEnabledByDefault: true); |
There was a problem hiding this comment.
PR description mentions a new diagnostic AWSLambda0132 for invalid DynamoDBEventAttribute, but in code the new diagnostic is AWSLambda0137 (and AWSLambda0132 is already used for Invalid ALBApiAttribute). Update the PR description (or the diagnostic ID, if the description is authoritative) to avoid confusion for users.
Summary
Adds
[DynamoDBEvent]annotation attribute support to the Lambda Annotations framework, enabling developers to declaratively configure DynamoDB stream-triggered Lambda functions directly in C# code. The source generator automatically produces the corresponding SAM/CloudFormation template configuration at build time.User Experience
With this change, developers can write DynamoDB stream-triggered Lambda functions like this:
The source generator will automatically generate the SAM template entry:
Attribute Properties
Stream@TableNamefor CloudFormation Ref, or stream ARN)ResourceNameBatchSize100StartingPositionLATESTorTRIM_HORIZONLATESTMaximumBatchingWindowInSecondsFiltersEnabledtrueCompile-Time Validation
The source generator validates at build time:
@(CloudFormation resource) or be a valid DynamoDB stream ARNDynamoDBEvent, optional second parameter must beILambdaContextvoidorTaskAmazon.Lambda.DynamoDBEventsNuGet packageLATESTorTRIM_HORIZONExample with all properties
What Changed
Annotation Attribute (
Amazon.Lambda.Annotations)DynamoDBEventAttributeclass inAmazon.Lambda.Annotations.DynamoDBnamespace with configurable properties and built-in validationSource Generator (
Amazon.Lambda.Annotations.SourceGenerator)DynamoDBEventAttributeBuilder— extracts attribute data from Roslyn syntax treeAttributeModelBuilder— recognizes and routes DynamoDBEvent attributesEventTypeBuilder— maps toEventType.DynamoDBSyntaxReceiver— registers DynamoDBEvent as a recognized attributeTypeFullNames— adds DynamoDB type constantsLambdaFunctionValidator— validates method signatures, return types, dependencies, and attribute propertiesCloudFormationWriter.ProcessDynamoDBAttribute()— generates SAM template with Stream (GetAtt StreamArn or ARN), StartingPosition, BatchSize, MaximumBatchingWindowInSeconds, FilterCriteria, and EnabledAWSLambda0132for invalid DynamoDBEventAttribute errorsTests
Related: DOTNET-8571